package org.gcb.core.model;

import java.util.HashSet;
import java.util.Set;
import javax.swing.event.SwingPropertyChangeSupport;
import org.gcb.core.model.util.Utilities;

/**
 *
 * @author Jason
 */
public class Defense {
    public static final String PROP_DESCRIPTION = "description";
    public static final String PROP_ABILITY_TYPE = "abilityType";
    public static final String PROP_MODIFIERS = "modifiers";
    private final EDefense defenseType;
    private final Toon toon;
    private final String text;
    private final AbilityType defaultAbilityType;
    private AbilityType abilityType;
    private String description = "[None]";
    private Set<MiscModifier<?, Integer>> modifiers = new HashSet<MiscModifier<?, Integer>>(0);
    private SwingPropertyChangeSupport propSupport = new SwingPropertyChangeSupport(this, true);

    public Defense(Toon toon, EDefense defenseType, AbilityType defaultAbilityType) {
        this.toon = toon;
        this.defenseType = defenseType;
        this.text = defenseType.getText();
        this.defaultAbilityType = defaultAbilityType;
        this.abilityType = defaultAbilityType;
    }

    public Defense(Toon toon, EDefense defenseType) {
        this.defenseType = defenseType;
        this.text = defenseType.getText();
        this.toon = toon;
        this.defaultAbilityType = null;
    }

    public String getText() {
        return text;
    }

    public AbilityType getAbilityType() {
        return abilityType;
    }

    public void setAbilityType(AbilityType abilityType) {
        AbilityType oldAbilityType= this.abilityType;
        this.abilityType = abilityType;
        propSupport.firePropertyChange(PROP_ABILITY_TYPE, oldAbilityType, this.abilityType);
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        String oldDescription= this.description;
        this.description = description;
        propSupport.firePropertyChange(PROP_DESCRIPTION, oldDescription, this.description);
    }

    public Set<MiscModifier<?, Integer>> getModifiers() {
        return modifiers;
    }

    public void setModifiers(Set<MiscModifier<?, Integer>> modifiers) {
        Set<MiscModifier<?, Integer>> oldModifiers= this.modifiers;
        this.modifiers = modifiers;
        propSupport.firePropertyChange(PROP_MODIFIERS, oldModifiers, this.modifiers);
    }

    public AbilityType getDefaultAbilityType() {
        return determineAbilityForDefense(defenseType, true);
    }

    public EDefense getDefenseType() {
        return defenseType;
    }

    /**
     * Use this method when needing to set the {@link AbilityType} to its original value based on instantiation.
     */
    public void revertAbilityType() {
        setAbilityType(defaultAbilityType);
    }

    /**
     * This method returns the mathematical output from calculating this Defense object.
     * Depending on the EDefense type assigned to this Defense object will determine
     * what values are taken into consideration. The following list and table relays this
     * information. The shield bonus is taken from the ARMS slot as that is what slot
     * the shield is placed.
     * <br>
     * <ul>
     * <li>{@code halfLevel = toon.getLevel()/2}</li>
     * <li>{@code abilityModifier = toon.getAbilities().get(abilityType).getModifier()}</li>
     * <li>{@code toon.getEquipment().getWorn().getArmorBonus()}</li>
     * <li>{@code toon.getEquipment().getWorn().get(EArmorSlot.ARMS).getArmorBonus()}</li>
     * <li>classBonus to be implemented (is currently 0)</li>
     * <li>{@code miscModifier = Utilities.calculateMiscModifier(modifiers)}</li>
     * </ul>
     * <br>
     * <table border=1>
     * <tr><th>{@link EDefense}</th><th>Formula</th></tr>
     * <tr><td>{@link EDefense#AC AC}</td><td><pre>10 + halfLevel + abilityMod + armorBonus + miscMods + shieldBonus</pre></td></tr>
     * <tr><td>{@link EDefense#FORT FORT}</td><td><pre>10 + halfLevel + abilityMod + classBonus + miscMods</pre></td></tr>
     * <tr><td>{@link EDefense#REF REF}</td><td><pre>10 + halfLevel + abilityMod + classBonus + miscMods + shieldBonus</pre></td></tr>
     * <tr><td>{@link EDefense#WILL WILL}</td><td><pre>10 + halfLevel + abilityMod + classBonus + miscMods</pre></td></tr>
     * </table>
     * @return
     */
    public Integer getScore() {
        Integer score = 0;
        Integer halfLevel = toon.getLevel()/2;
        Integer abilityModifier = toon.getAbilities().get(determineAbilityForDefense(defenseType, false)).getModifier();
        Integer armorBonus = toon.getEquipment().getWorn().getArmorBonus();
        Integer shieldBonus = toon.getEquipment().getWorn().isEmpty(EArmorSlot.ARMS)?0:toon.getEquipment().getWorn().get(EArmorSlot.ARMS).getArmorBonus();
        Integer classBonus = 0;//TODO to be implemented via toon.getClasses().getDefenseBonus(EDefense defenseType);
        Integer miscModifier = 0;//Utilities.calculateMiscModifier(modifiers);
        switch(defenseType){
            case AC:
                score += shieldBonus;
                score += armorBonus;
                break;
            case REF:
                score += shieldBonus;
            case FORT:
            case WILL:
                score += classBonus;
        }
        score += 10;
        score += halfLevel;
        score += abilityModifier;
        score += miscModifier;
        return score;
    }

    /**
     * Based on the type of defense, an ability type will be returned.
     * Most notably used in the calculation of the defense score.
     * The way this works is any defense has two ability types. The
     * default which is based on calculations, class, or race settings,
     * and the override which can be anything. Scenarios like magical
     * buffs or debuffs, special equipment, might <i>change</i> the
     * way a certain defense acts.
     * If the override ability type is null or the same as the default,
     * the default ability type is used. Otherwise, the overriding
     * ability type is used.
     * @param defenseType
     * @param ignoreOverride
     * @return the determined {@link AbilityType}
     */
    private AbilityType determineAbilityForDefense(EDefense defenseType, boolean ignoreOverride) {
        if((abilityType!=null || abilityType!=defaultAbilityType) && !ignoreOverride)
            return abilityType;
        switch(defenseType) {
            case FORT:
                Integer stre = toon.getAbilities().get(AbilityType.STR).getModifier();
                Integer cons = toon.getAbilities().get(AbilityType.CON).getModifier();
                return stre>cons?AbilityType.STR:AbilityType.CON;
            case REF:
                Integer dext = toon.getAbilities().get(AbilityType.DEX).getModifier();
                Integer inte = toon.getAbilities().get(AbilityType.INT).getModifier();
                return dext>inte?AbilityType.DEX:AbilityType.INT;
            case WILL:
                Integer wisd = toon.getAbilities().get(AbilityType.WIS).getModifier();
                Integer cha = toon.getAbilities().get(AbilityType.CHA).getModifier();
                return wisd>cha?AbilityType.WIS:AbilityType.CHA;
            case AC:
                //TODO need to change how this is calculated once race or class is implemented
                return AbilityType.STR;
            default:
                throw new IllegalArgumentException("Must pass in a EDefense type.");
        }
    }
}
